#include "Buffer.h"
#include "utility/assertion.h"
#include "utility/cast.h"
#include "wrap/uio.h"

#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <sys/uio.h> // for struct iovec

namespace _ {

static std::array<struct iovec, 2> create_iovec(
    void *base, std::size_t len, void *extraBase, std::size_t extraLen) {
    std::array<struct iovec, 2> result{};
    result.at(0).iov_base = base;
    result.at(0).iov_len = len;
    result.at(1).iov_base = extraBase;
    result.at(1).iov_len = extraLen;
    return result;
}

}

Buffer::Buffer(std::size_t initSize):
    _buffer{Container{_PREPEND_SIZE + initSize, '\0', Container::allocator_type{}}},
    _indexReader{_PREPEND_SIZE},
    _indexWriter{_PREPEND_SIZE}
{
    assert(!readable_bytes());
    assert(writable_bytes() == _INIT_SIZE);
    assert(preprendable_bytes() == _PREPEND_SIZE);
}

Buffer::~Buffer() = default;

void Buffer::append(const char *data, size_type size) {
    ensure_writable_bytes(size);
    std::copy(data, data + size, &_buffer.at(_indexWriter));
    has_written(size);
}

void Buffer::ensure_writable_bytes(size_type size) {
    if (writable_bytes() >= size) {
        return;
    }
    if (writable_bytes() + preprendable_bytes() < size + _PREPEND_SIZE) {
        _buffer.resize(_indexWriter + size);
    }
    else {
        assert(_PREPEND_SIZE < _indexWriter);
        const auto bytes = readable_bytes();
        std::copy(
            Cast::PADD(_buffer.cbegin(), _indexReader),
            Cast::PADD(_buffer.cbegin(), _indexWriter),
            Cast::PADD(_buffer.begin(), _PREPEND_SIZE));
        _indexReader = _PREPEND_SIZE;
        _indexWriter = _indexReader + bytes;
        assert(bytes == readable_bytes());
    }
    assert(writable_bytes() >= size);
}

const char *Buffer::find_CRLF(const char *start) const
{
    assert(peek() <= start);
    assert(start <= begin_write());
    const auto *crlf = std::search(start, begin_write(), CRLF, CRLF + CRLF_SIZE);
    return crlf == begin_write() ? nullptr : crlf;
}

void Buffer::has_written(size_type size)
{
    assert(writable_bytes() >= size);
    _indexWriter += size;
}

char Buffer::peek_char() const
{
    assert(readable_bytes() >= sizeof(char));
    return *peek();
}

uint16_t Buffer::peek_uint16() const
{
    assert(readable_bytes() >= sizeof(uint16_t));
    uint16_t result = 0;
    ::memcpy(&result, peek(), sizeof(result));
    return result;
}

uint32_t Buffer::peek_uint32() const
{
    assert(readable_bytes() >= sizeof(uint32_t));
    uint32_t result = 0;
    ::memcpy(&result, peek(), sizeof(result));
    return result;
}

uint64_t Buffer::peek_uint64() const
{
    assert(readable_bytes() >= sizeof(uint64_t));
    uint64_t result = 0;
    ::memcpy(&result, peek(), sizeof(result));
    return result;
}

void Buffer::prepend(const void *data, size_type size)
{
    assert(size <= preprendable_bytes());
    _indexReader -= size;
    const auto *const d = static_cast<const char *>(data);
    std::copy(d, d + size, _buffer.data() + _indexReader);
}

char Buffer::read_char()
{
    const auto result = peek_char();
    retrieve_type<char>();
    return result;
}

ssize_t Buffer::read_fd(int fd, int &savedErrno)
{
    std::array<char, 1 << 16> buffer{};
    const auto bytes = writable_bytes();
    auto vec = _::create_iovec(&_buffer.at(_indexWriter), bytes, buffer.data(), buffer.size());
    const auto n = Wrap::readv_(fd, vec.data(), (bytes < buffer.size()) ? 2 : 1);
    if (n < 0) {
        savedErrno = errno;
    }
    else if (Cast::GE(bytes, n)) {
        Cast::iadd(_indexWriter, n);
    }
    else {
        _indexWriter = _buffer.size();
        append(buffer.data(), static_cast<std::size_t>(n) - bytes);
    }
    return n;
}

uint16_t Buffer::read_uint16()
{
    const auto result = peek_uint16();
    retrieve_type<uint16_t>();
    return result;
}

uint32_t Buffer::read_uint32()
{
    const auto result = peek_uint32();
    retrieve_type<uint32_t>();
    return result;
}

uint64_t Buffer::read_uint64()
{
    const auto result = peek_uint64();
    retrieve_type<uint64_t>();
    return result;
}

void Buffer::retrieve()
{
    _indexReader = _PREPEND_SIZE;
    _indexWriter = _PREPEND_SIZE;
}

void Buffer::retrieve(size_type size)
{
    assert(size <= readable_bytes());
    if (size < readable_bytes()) {
        _indexReader += size;
        return;
    }
    retrieve();
}

std::string Buffer::retrieve_as_string(size_type size)
{
    assert(size <= readable_bytes());
    std::string result(peek(), peek() + size);
    retrieve(size);
    return result;
}

void Buffer::retrieve_until(const char *end)
{
    assert(peek() < end);
    assert(end <= begin_write());
    retrieve(static_cast<Buffer::size_type>(end - peek()));
}

void Buffer::swap(Buffer &rhs) noexcept
{
    using std::swap;
    _buffer.swap(rhs._buffer);
    swap(_indexReader, rhs._indexReader);
    swap(_indexWriter, rhs._indexWriter);
}
